import datetime

from selenium import webdriver
import platform
import time
import random
from PIL import Image
from urllib.request import urlretrieve
from selenium.webdriver.common.action_chains import ActionChains
import requests
import os
import time

# 请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 ',
    'Referer': 'https://qzone.qq.com/',
    'Host': 'user.qzone.qq.com',
    'Accept-Language': 'zh-CN,zh;q=0.9'
}


# 返回带登录Cookie的会话窗口
def get_qzone_browse_with_cookies(number, password):
    opt = webdriver.ChromeOptions()
    """
    linux下需添加此2个参数：--headless、--no-sandbox
    windows无需添加
    """
    if platform.system().lower() == 'linux':
        opt.add_argument('--headless')  # 隐藏浏览器
        opt.add_argument('--no-sandbox')  # 取消沙盒模式运行
        opt.add_argument("--disable-setuid-sandbox")
        opt.add_argument('--disable-gpu')  # 谷歌文档提到需要加上这个属性来规避bug
    # opt.add_argument('–-disable-images')  # 禁用图片，以节省流量
    opt.add_argument('lang=zh_CN.UTF-8')
    print("创建浏览器会话")
    browser = webdriver.Chrome(executable_path='/usr/bin/chromedriver', options=opt)
    browser.get('http://qzone.qq.com')
    print("尝试使用账号密码登录")
    browser.switch_to.frame('login_frame')
    browser.find_element_by_id('switcher_plogin').click()
    browser.find_element_by_id('u').send_keys(number)
    browser.find_element_by_id('p').send_keys(password)
    print("填写账号密码成功")
    try:
        browser.find_element_by_id("login_button").click()
    except Exception as e:
        print("点击登录按钮失败：{}".format(e))
    time.sleep(3)
    print("尝试破解滑动验证码")
    unlock_slide_code(browser)
    try:
        time.sleep(2)
        browser.switch_to.frame('login_frame')
    except Exception as e:
        print("切换登录窗口失败：{}".format(e))
    try:
        browser.find_element_by_id("login_button").click()
    except Exception as e:
        print("点击登录按钮失败：{}".format(e))
    time.sleep(3)

    print("尝试破解密保手机验证码")
    verify_phone_code(browser)
    try:
        time.sleep(2)
        browser.switch_to.frame('login_frame')
    except Exception as e:
        print("切换登录窗口失败：{}".format(e))
    try:
        browser.find_element_by_id("login_button").click()
    except Exception as e:
        print("点击登录按钮失败：{}".format(e))

    time.sleep(3)
    login_flag = False
    if browser.title != 'QQ空间-分享生活，留住感动':
        # 修改标志，表示用户已登录
        login_flag = True
    else:
        # 开始放大招，使用100%成功的扫码登录
        try:
            '''
            获取是否需要进行扫码登录
                腾讯对环境验证有三条
                    1.滑动验证码
                    2.手机验证码
                    3.提示网络环境异常，请用手Q扫码登录
                其中第3条：扫码登录是100%可以登录上去

            '''
            try:
                text = browser.find_element_by_id('err_m').text
                print(text)
            except Exception as e:
                print("读取错误消息：{} ".format(e))
            print("检测到需进行扫码登录")
            print('获取登录二维码中')
            browser.get('http://qzone.qq.com')
            browser.refresh()
            browser.switch_to.frame('login_frame')
            code_src = browser.find_element_by_id('qrlogin_img').get_attribute('src')
            downLoadImage('code.png', code_src)
            browser.save_screenshot('code_screen.png')
            print('扫码登录二维码下载完毕： code.png/code_screen.png  若code.png失效，请扫code_screen.png进行登录')
            print('是否扫码完毕？[y]?')
            print('休息30秒 等待用户扫码登录 请在30秒内扫码成功')
            time.sleep(30)
            if browser.title != 'QQ空间-分享生活，留住感动':
                # 修改标志，表示用户已登录
                login_flag = True
        except Exception as e:
            print("扫码二维码登录失败：{}".format(e))
    if not login_flag:
        print("未登录成功 程序停止 请手动重启程序")
        browser.quit()
        return None
    return browser


# 使用requests下载登录二维码
def downLoadImage(name, url):
    r = requests.get(url)
    #print r.content
    with open(name, "wb") as f:
        f.write(r.content)
        f.close()


# 校验密保手机号验证码
def verify_phone_code(browser):
    try:
        browser.switch_to.frame('verify')
        # 破解滑动验证码
        print("请输入QQ号对应的密保手机号：")
        phone = input()
        phone_input = browser.find_element_by_css_selector('input[type="number"]')
        phone_input.send_keys(phone)
        # 获取点击发送验证码按钮
        browser.find_element_by_class_name('get-sms').click()
        print("请输入您的收到的验证码：")
        code = input()
        browser.find_element_by_class_name('submit').click()

    except Exception as e:
        print(e)
        print("未检测到手机验证码 或 破解失败")


# 破解滑动验证码
def unlock_slide_code(browser):

    try:
        try:
            browser.switch_to.frame('tcaptcha_iframe')
        except Exception as e:
            print("switch_to.frame('tcaptcha_iframe')失败！")
        # 破解滑动验证码
        print("获取验证码图片")
        a, b = get_img(browser)
        a = resize_img(a)
        b = resize_img(b)
        print("获取验证码偏移值")
        distance = get_gap_offset(a, b)
        track = get_track(distance)
        print("验证码破解中")
        operate_slider(browser, track)

        print(browser.find_element_by_id('err_m').text)

    except Exception as e:
        print(e)
        print("未检测到滑动验证码 或 破解失败")


# 下载登录二维码
def get_img(browser):
    """
    获取验证码阴影图和原图
    :return:
    """
    # browser.switch_to.frame('tcaptcha_iframe')
    time.sleep(3)
    # 获取有阴影的图片
    src = browser.find_element_by_id('slideBg').get_attribute('src')
    # 分析图片地址，发现原图地址可以通过阴影图地址改动获取 只需要修改一下图片路径中的index
    #print(src)
    src_bg=src.replace('index=1','index=0')
    src_bg=src_bg.replace('img_index=1','img_index=0')
    #print(src_bg)
    # 将图片下载到本地
    urlretrieve(src, 'img1.png')
    urlretrieve(src_bg, 'img2.png')
    #读取本地图片
    captcha1 = Image.open('img1.png')
    captcha2 = Image.open('img2.png')
    return captcha1, captcha2


# 重置滑动验证码的尺寸为相同
def resize_img(img):
    """
    下载的图片把网页中的图片进行了放大，所以将图片还原成原尺寸
    :param img: 图片
    :return: 返回还原后的图片
    """
    # 通过本地图片与原网页图片的比较，计算出的缩放比例 原图（680x390）缩小图（280x161）
    a = 2.428
    (x, y) = img.size
    x_resize = int(x // a)
    y_resize = int(y // a)
    """
    Image.NEAREST ：低质量
    Image.BILINEAR：双线性
    Image.BICUBIC ：三次样条插值
    Image.ANTIALIAS：高质量
    """
    img = img.resize((x_resize, y_resize), Image.ANTIALIAS)
    return img

def is_pixel_equal(img1, img2, x, y):
    """
    比较两张图片同一点上的像数值，差距大于设置标准返回False
    :param img1: 阴影图
    :param img2: 原图
    :param x: 横坐标
    :param y: 纵坐标
    :return: 是否相等
    """
    pixel1, pixel2 = img1.load()[x, y], img2.load()[x, y]
    sub_index = 100
    #比较RGB各分量的值
    if abs(pixel1[0] - pixel2[0]) < sub_index and abs(pixel1[1] - pixel2[1]) < sub_index and abs(
            pixel1[2] - pixel2[2]) < sub_index:
        return True
    else:
        return False

def get_gap_offset(img1, img2):
    """
    获取缺口的偏移量
    """
    distance = 70
    for i in range(distance, img1.size[0]):
        for j in range(img1.size[1]):
            # 两张图片对比,(i,j)像素点的RGB差距，过大则该x为偏移值
            if not is_pixel_equal(img1, img2, i, j):
                distance = i
                return distance
    return distance

def get_track(distance):
    """
    计算滑块的移动轨迹
    """
    # 通过观察发现滑块并不是从0开始移动，有一个初始值
    distance -= 30
    a = distance / 4
    track = [a, a, a, a]
    return track

def shake_mouse(browser):
    """
    模拟人手释放鼠标抖动
    """
    ActionChains(browser).move_by_offset(xoffset=-2, yoffset=0).perform()
    ActionChains(browser).move_by_offset(xoffset=2, yoffset=0).perform()

def operate_slider(browser, track):
    """
    拖动滑块
    当你调用ActionChains的方法时，不会立即执行，而是会将所有的操作按顺序存放在一个队列里，当你调用perform()方法时，队列中的时间会依次执行。
    :param track: 运动轨迹
    :return:
    """
    # 定位到拖动按钮
    slider_bt = browser.find_element_by_xpath('//div[@class="tc-drag-thumb"]')
    # 点击拖动按钮不放
    ActionChains(browser).click_and_hold(slider_bt).perform()
    # 按正向轨迹移动
    # move_by_offset函数是会延续上一步的结束的地方开始移动
    for i in track:
        ActionChains(browser).move_by_offset(xoffset=i, yoffset=0).perform()
        print("滑动验证码移动距离：{}".format(i))
        time.sleep(random.random() / 100)  # 每移动一次随机停顿0-1/100秒之间骗过了极验，通过率很高
    time.sleep(random.random())
    # 按逆向轨迹移动
    back_tracks = [-1, -0.5, -1]
    for i in back_tracks:
        time.sleep(random.random() / 100)
        ActionChains(browser).move_by_offset(xoffset=i, yoffset=0).perform()
    # 模拟人手抖动
    shake_mouse(browser)
    time.sleep(random.random())
    # 松开滑块按钮
    ActionChains(browser).release().perform()



# 自动读取QQ 及 密码
def read_number_and_password():
    with open("np.txt") as np:
        number = np.readline()
        password = np.readline()
        print("读取到账号：{}  密码：{}".format(number,password))
        return number, password


# 获取browser
def get_browser():
    number, password = read_number_and_password()
    browser = None
    count = 0
    browser = get_qzone_browse_with_cookies(number, password)
    # while browser is None and count < 10:
    #     count = count + 1
    #     browser = get_qzone_browse_with_cookies(number, password)
    return browser


# 自动点赞程序
def auto_dianzan(browser):
    # 获取说说当下待点赞和已点赞的列表
    elements = browser.find_elements_by_class_name('qz_like_btn_v3')
    # print('获取到点赞列表：{}'.format(len(elements)))
    for element in elements:
        # assert isinstance(element,)
        if element.get_attribute('data-clicklog') == 'like':
            element.click()
            # 点赞
            data_curkey = str(element.get_attribute('data-curkey'))
            index = data_curkey.find('/mood/')
            if index == -1:
                print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "点赞成功： 未知")
                continue
            href = data_curkey[:index]
            nicknames = [i.text for i in browser.find_elements_by_css_selector('a[href="{}"]'.format(href)) if i.text != '']
            if len(nicknames) > 0:
                nickname = nicknames[0]
                print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "点赞成功：{}".format(nickname))

        else:
            pass
            # 取消点赞
            # data_curkey = str(element.get_attribute('data-curkey'))
            # index = data_curkey.find('/mood/')
            # if index == -1:
            #     print("已点赞： 未知")
            #     continue
            # href = data_curkey[:index]
            # nicknames = [i.text for i in browser.find_elements_by_css_selector('a[href="{}"]'.format(href)) if i.text != '']
            # if len(nicknames) > 0:
            #     nickname = nicknames[0]
            #     print("已点赞：{}".format(nickname))

    return True

def auto_main(browser):
    try:
        if browser is not None:
            while True:
                # 休息1秒 等待资源加载完毕
                time.sleep(3)
                # 将屏幕滚动到10000的位置，获取更多的说说, 并休息3秒
                browser.execute_script('document.documentElement.scrollTop=10000')
                time.sleep(1)
                # 回到顶部，划到视窗之内
                browser.execute_script('document.documentElement.scrollTop=0')
                time.sleep(3)
                auto_dianzan(browser)
                # 刷新页面
                # print('刷新页面')
                time.sleep(30)
                browser.refresh()
        else:
            # todo 发送邮件 提示登录失败
            pass
    except Exception as e:
        print("全局错误：{}".format(e))
        browser.refresh()
        # browser.save_screenshot('error' + str(datetime.datetime.now().strftime('%Y%m%d__%H_%M_%S')) +  '.png')
        auto_main(browser)


def rm_files():
    file_paths = ['code.png','code_screen.png','img1.png','img2.png']
    for file_path in file_paths:
        if os.path.exists('file_path'):
            os.remove('file_path')
            print('已删除文件：{}'.format(file_path))


if __name__ == '__main__':
    rm_files()
    browser = get_browser()
    auto_main(browser)

